﻿using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using FairyGUI.Utils;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace FairyGUI
{
	/// <summary>
	/// A UI Package contains a description file and some texture,sound assets.
	/// </summary>
	public class UIPackage
	{
		/// <summary>
		/// Unload UIAssetBundle by FairyGUI system.
		/// if use AssetBundlesManager set value to false
		/// then unload UIAssetBundle by AssetBundlesManager
		/// </summary>
		public static bool unloadBundleByFGUI = true;

		/// <summary>
		/// Package id. It is generated by the Editor, or set by customId.
		/// </summary>
		public string id { get; private set; }

		/// <summary>
		/// Package name.
		/// </summary>
		public string name { get; private set; }

		/// <summary>
		/// The path relative to the resources folder.
		/// </summary>
		public string assetPath { get; private set; }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="extension"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		public delegate object LoadResource(string name, string extension, System.Type type, out DestroyMethod destroyMethod);

		/// <summary>
		/// 
		/// </summary>
		/// <param name="result"></param>
		public delegate void CreateObjectCallback(GObject result);

		List<PackageItem> _items;
		Dictionary<string, PackageItem> _itemsById;
		Dictionary<string, PackageItem> _itemsByName;
		Dictionary<string, string>[] _dependencies;
		AssetBundle _resBundle;
		string _customId;
		bool _fromBundle;
		LoadResource _loadFunc;

		class AtlasSprite
		{
			public PackageItem atlas;
			public Rect rect = new Rect();
			public bool rotated;
		}
		Dictionary<string, AtlasSprite> _sprites;

		static Dictionary<string, UIPackage> _packageInstById = new Dictionary<string, UIPackage>();
		static Dictionary<string, UIPackage> _packageInstByName = new Dictionary<string, UIPackage>();
		static List<UIPackage> _packageList = new List<UIPackage>();

		internal static int _constructing;

		public const string URL_PREFIX = "ui://";

		public UIPackage()
		{
			_items = new List<PackageItem>();
			_itemsById = new Dictionary<string, PackageItem>();
			_itemsByName = new Dictionary<string, PackageItem>();
			_sprites = new Dictionary<string, AtlasSprite>();
		}

		/// <summary>
		/// Return a UIPackage with a certain id.
		/// </summary>
		/// <param name="id">ID of the package.</param>
		/// <returns>UIPackage</returns>
		public static UIPackage GetById(string id)
		{
			UIPackage pkg;
			if (_packageInstById.TryGetValue(id, out pkg))
				return pkg;
			else
				return null;
		}

		/// <summary>
		/// Return a UIPackage with a certain name.
		/// </summary>
		/// <param name="name">Name of the package.</param>
		/// <returns>UIPackage</returns>
		public static UIPackage GetByName(string name)
		{
			UIPackage pkg;
			if (_packageInstByName.TryGetValue(name, out pkg))
				return pkg;
			else
				return null;
		}

		/// <summary>
		/// Add a UI package from assetbundle.
		/// </summary>
		/// <param name="bundle">A assetbundle.</param>
		/// <returns>UIPackage</returns>
		public static UIPackage AddPackage(AssetBundle bundle)
		{
			return AddPackage(bundle, bundle, null);
		}

		/// <summary>
		/// Add a UI package from two assetbundles. desc and res can be same.
		/// </summary>
		/// <param name="desc">A assetbunble contains description file.</param>
		/// <param name="res">A assetbundle contains resources.</param>
		/// <returns>UIPackage</returns>
		public static UIPackage AddPackage(AssetBundle desc, AssetBundle res)
		{
			return AddPackage(desc, res, null);
		}

		/// <summary>
		/// Add a UI package from two assetbundles with a optional main asset name.
		/// </summary>
		/// <param name="desc">A assetbunble contains description file.</param>
		/// <param name="res">A assetbundle contains resources.</param>
		/// <param name="mainAssetName">Main asset name. e.g. Basics_fui.bytes</param>
		/// <returns>UIPackage</returns>
		public static UIPackage AddPackage(AssetBundle desc, AssetBundle res, string mainAssetName)
		{
			byte[] source = null;
#if (UNITY_5 || UNITY_5_3_OR_NEWER)
			if (mainAssetName != null)
			{
				TextAsset ta = desc.LoadAsset<TextAsset>(mainAssetName);
				if (ta != null)
					source = ta.bytes;
			}
			else
			{
				string[] names = desc.GetAllAssetNames();
				string searchPattern = "_fui";
				foreach (string n in names)
				{
					if (n.IndexOf(searchPattern) != -1)
					{
						TextAsset ta = desc.LoadAsset<TextAsset>(n);
						if (ta != null)
						{
							source = ta.bytes;
							mainAssetName = Path.GetFileNameWithoutExtension(n);
							break;
						}
					}
				}
			}
#else
			if (mainAssetName != null)
			{
				TextAsset ta = (TextAsset)desc.Load(mainAssetName, typeof(TextAsset));
				if (ta != null)
					source = ta.bytes;
			}
			else
			{
				source = ((TextAsset)desc.mainAsset).bytes;
				mainAssetName = desc.mainAsset.name;
			}
#endif
			if (source == null)
				throw new Exception("FairyGUI: no package found in this bundle.");

			if (desc != res)
				desc.Unload(true);

			ByteBuffer buffer = new ByteBuffer(source);

			UIPackage pkg = new UIPackage();
			pkg._resBundle = res;
			pkg._fromBundle = true;
			int pos = mainAssetName.IndexOf("_fui");
			string assetNamePrefix;
			if (pos != -1)
				assetNamePrefix = mainAssetName.Substring(0, pos);
			else
				assetNamePrefix = mainAssetName;
			if (!pkg.LoadPackage(buffer, res.name, assetNamePrefix))
				return null;

			_packageInstById[pkg.id] = pkg;
			_packageInstByName[pkg.name] = pkg;
			_packageList.Add(pkg);

			return pkg;
		}

		/// <summary>
		/// Add a UI package from a path relative to Unity Resources path.
		/// </summary>
		/// <param name="descFilePath">Path relative to Unity Resources path.</param>
		/// <returns>UIPackage</returns>
		public static UIPackage AddPackage(string descFilePath)
		{
			if (descFilePath.StartsWith("Assets/"))
			{
#if UNITY_EDITOR
				return AddPackage(descFilePath, (string name, string extension, System.Type type, out DestroyMethod destroyMethod) =>
				{
					destroyMethod = DestroyMethod.Unload;
					return AssetDatabase.LoadAssetAtPath(name + extension, type);
				});
#else

				Debug.LogWarning("FairyGUI: failed to load package in '" + descFilePath + "'");
				return null;
#endif
			}
			return AddPackage(descFilePath, (string name, string extension, System.Type type, out DestroyMethod destroyMethod) =>
			{
				destroyMethod = DestroyMethod.Unload;
				return Resources.Load(name, type);
			});
		}

		/// <summary>
		/// 使用自定义的加载方式载入一个包。
		/// </summary>
		/// <param name="assetPath">包资源路径。</param>
		/// <param name="loadFunc">载入函数</param>
		/// <returns></returns>
		public static UIPackage AddPackage(string assetPath, LoadResource loadFunc)
		{
			if (_packageInstById.ContainsKey(assetPath))
				return _packageInstById[assetPath];

			DestroyMethod dm;
			TextAsset asset = (TextAsset)loadFunc(assetPath + "_fui", ".bytes", typeof(TextAsset), out dm);
			if (asset == null)
			{
				if (Application.isPlaying)
					throw new Exception("FairyGUI: Cannot load ui package in '" + assetPath + "'");
				else
					Debug.LogWarning("FairyGUI: Cannot load ui package in '" + assetPath + "'");
			}

			ByteBuffer buffer = new ByteBuffer(asset.bytes);

			UIPackage pkg = new UIPackage();
			pkg._loadFunc = loadFunc;
			pkg.assetPath = assetPath;
			if (!pkg.LoadPackage(buffer, assetPath, assetPath))
				return null;

			_packageInstById[pkg.id] = pkg;
			_packageInstByName[pkg.name] = pkg;
			_packageInstById[assetPath] = pkg;
			_packageList.Add(pkg);
			return pkg;
		}

		/// <summary>
		/// 使用自定义的加载方式载入一个包。
		/// </summary>
		/// <param name="descData">描述文件数据。</param>
		/// <param name="assetNamePrefix">资源文件名前缀。如果包含，则载入资源时名称将传入assetNamePrefix@resFileName这样格式。可以为空。</param>
		/// <param name="loadFunc">载入函数</param>
		/// <returns></returns>
		public static UIPackage AddPackage(byte[] descData, string assetNamePrefix, LoadResource loadFunc)
		{
			ByteBuffer buffer = new ByteBuffer(descData);

			UIPackage pkg = new UIPackage();
			pkg._loadFunc = loadFunc;
			if (!pkg.LoadPackage(buffer, "raw data", assetNamePrefix))
				return null;

			_packageInstById[pkg.id] = pkg;
			_packageInstByName[pkg.name] = pkg;
			_packageList.Add(pkg);

			return pkg;
		}

		/// <summary>
		/// Remove a package. All resources in this package will be disposed.
		/// </summary>
		/// <param name="packageIdOrName"></param>
		/// <param name="allowDestroyingAssets"></param>
		public static void RemovePackage(string packageIdOrName)
		{
			UIPackage pkg = null;
			if (!_packageInstById.TryGetValue(packageIdOrName, out pkg))
			{
				if (!_packageInstByName.TryGetValue(packageIdOrName, out pkg))
					throw new Exception("FairyGUI: '" + packageIdOrName + "' is not a valid package id or name.");
			}
			pkg.Dispose();
			_packageInstById.Remove(pkg.id);
			if (pkg._customId != null)
				_packageInstById.Remove(pkg._customId);
			if (pkg.assetPath != null)
				_packageInstById.Remove(pkg.assetPath);
			_packageInstByName.Remove(pkg.name);
			_packageList.Remove(pkg);
		}

		/// <summary>
		/// 
		/// </summary>
		public static void RemoveAllPackages()
		{
			if (_packageInstById.Count > 0)
			{
				UIPackage[] pkgs = _packageList.ToArray();

				foreach (UIPackage pkg in pkgs)
				{
					pkg.Dispose();
				}
			}
			_packageList.Clear();
			_packageInstById.Clear();
			_packageInstByName.Clear();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public static List<UIPackage> GetPackages()
		{
			return _packageList;
		}

		/// <summary>
		/// Create a UI object.
		/// </summary>
		/// <param name="pkgName">Package name.</param>
		/// <param name="resName">Resource name.</param>
		/// <returns>A UI object.</returns>
		public static GObject CreateObject(string pkgName, string resName)
		{
			UIPackage pkg = GetByName(pkgName);
			if (pkg != null)
				return pkg.CreateObject(resName);
			else
				return null;
		}

		/// <summary>
		///  Create a UI object.
		/// </summary>
		/// <param name="pkgName">Package name.</param>
		/// <param name="resName">Resource name.</param>
		/// <param name="userClass">Custom implementation of this object.</param>
		/// <returns>A UI object.</returns>
		public static GObject CreateObject(string pkgName, string resName, System.Type userClass)
		{
			UIPackage pkg = GetByName(pkgName);
			if (pkg != null)
				return pkg.CreateObject(resName, userClass);
			else
				return null;
		}

		/// <summary>
		/// Create a UI object.
		/// </summary>
		/// <param name="url">Resource url.</param>
		/// <returns>A UI object.</returns>
		public static GObject CreateObjectFromURL(string url)
		{
			PackageItem pi = GetItemByURL(url);
			if (pi != null)
				return pi.owner.CreateObject(pi, null);
			else
				return null;
		}

		/// <summary>
		/// Create a UI object.
		/// </summary>
		/// <param name="url">Resource url.</param>
		/// <param name="userClass">Custom implementation of this object.</param>
		/// <returns>A UI object.</returns>
		public static GObject CreateObjectFromURL(string url, System.Type userClass)
		{
			PackageItem pi = GetItemByURL(url);
			if (pi != null)
				return pi.owner.CreateObject(pi, userClass);
			else
				return null;
		}

		public static void CreateObjectAsync(string pkgName, string resName, CreateObjectCallback callback)
		{
			UIPackage pkg = GetByName(pkgName);
			if (pkg != null)
				pkg.CreateObjectAsync(resName, callback);
			else
				Debug.LogError("FairyGUI: package not found - " + pkgName);
		}

		public static void CreateObjectFromURL(string url, CreateObjectCallback callback)
		{
			PackageItem pi = GetItemByURL(url);
			if (pi != null)
				AsyncCreationHelper.CreateObject(pi, callback);
			else
				Debug.LogError("FairyGUI: resource not found - " + url);
		}

		/// <summary>
		/// Get a asset with a certain name.
		/// </summary>
		/// <param name="pkgName">Package name.</param>
		/// <param name="resName">Resource name.</param>
		/// <returns>If resource is atlas, returns NTexture; If resource is sound, returns AudioClip.</returns>
		public static object GetItemAsset(string pkgName, string resName)
		{
			UIPackage pkg = GetByName(pkgName);
			if (pkg != null)
				return pkg.GetItemAsset(resName);
			else
				return null;
		}

		/// <summary>
		/// Get a asset with a certain name.
		/// </summary>
		/// <param name="url">Resource url.</param>
		/// <returns>If resource is atlas, returns NTexture; If resource is sound, returns AudioClip.</returns>
		public static object GetItemAssetByURL(string url)
		{
			PackageItem item = GetItemByURL(url);
			if (item == null)
				return null;

			return item.owner.GetItemAsset(item);
		}

		/// <summary>
		/// Get url of an item in package.
		/// </summary>
		/// <param name="pkgName">Package name.</param>
		/// <param name="resName">Resource name.</param>
		/// <returns>Url.</returns>
		public static string GetItemURL(string pkgName, string resName)
		{
			UIPackage pkg = GetByName(pkgName);
			if (pkg == null)
				return null;

			PackageItem pi;
			if (!pkg._itemsByName.TryGetValue(resName, out pi))
				return null;

			return URL_PREFIX + pkg.id + pi.id;
		}

		public static PackageItem GetItemByURL(string url)
		{
			if (url == null)
				return null;

			int pos1 = url.IndexOf("//");
			if (pos1 == -1)
				return null;

			int pos2 = url.IndexOf('/', pos1 + 2);
			if (pos2 == -1)
			{
				if (url.Length > 13)
				{
					string pkgId = url.Substring(5, 8);
					UIPackage pkg = GetById(pkgId);
					if (pkg != null)
					{
						string srcId = url.Substring(13);
						return pkg.GetItem(srcId);
					}
				}
			}
			else
			{
				string pkgName = url.Substring(pos1 + 2, pos2 - pos1 - 2);
				UIPackage pkg = GetByName(pkgName);
				if (pkg != null)
				{
					string srcName = url.Substring(pos2 + 1);
					return pkg.GetItemByName(srcName);
				}
			}

			return null;
		}

		/// <summary>
		/// 将'ui://包名/组件名'转换为以内部id表达的url格式。如果传入的url本身就是内部id格式，则直接返回。
		/// 同时这个方法还带格式检测，如果传入不正确的url，会返回null。
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		public static string NormalizeURL(string url)
		{
			if (url == null)
				return null;

			int pos1 = url.IndexOf("//");
			if (pos1 == -1)
				return null;

			int pos2 = url.IndexOf('/', pos1 + 2);
			if (pos2 == -1)
				return url;
			else
			{
				string pkgName = url.Substring(pos1 + 2, pos2 - pos1 - 2);
				string srcName = url.Substring(pos2 + 1);
				return GetItemURL(pkgName, srcName);
			}
		}

		/// <summary>
		/// Set strings source.
		/// </summary>
		/// <param name="source"></param>
		public static void SetStringsSource(XML source)
		{
			TranslationHelper.LoadFromXML(source);
		}

		/// <summary>
		/// Set a custom id for package, then you can use it in GetById.
		/// </summary>
		public string customId
		{
			get { return _customId; }
			set
			{
				if (_customId != null)
					_packageInstById.Remove(_customId);
				_customId = value;
				if (_customId != null)
					_packageInstById[_customId] = this;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public AssetBundle resBundle
		{
			get { return _resBundle; }
		}

		/// <summary>
		/// 获得本包依赖的包的id列表
		/// </summary>
		public Dictionary<string, string>[] dependencies
		{
			get { return _dependencies; }
		}

		bool LoadPackage(ByteBuffer buffer, string packageSource, string assetNamePrefix)
		{
			if (buffer.ReadUint() != 0x46475549)
			{
				if (Application.isPlaying)
					throw new Exception("FairyGUI: old package format found in '" + packageSource + "'");
				else
				{
					Debug.LogWarning("FairyGUI: old package format found in '" + packageSource + "'");
					return false;
				}
			}

			buffer.version = buffer.ReadInt();
			buffer.ReadBool(); //compressed
			id = buffer.ReadString();
			name = buffer.ReadString();
			if (_packageInstById.ContainsKey(id) && name != _packageInstById[id].name)
			{
				Debug.LogWarning("FairyGUI: Package id conflicts, '" + name + "' and '" + _packageInstById[id].name + "'");
				return false;
			}
			buffer.Skip(20);
			int indexTablePos = buffer.position;
			int cnt;

			buffer.Seek(indexTablePos, 4);

			cnt = buffer.ReadInt();
			string[] stringTable = new string[cnt];
			for (int i = 0; i < cnt; i++)
				stringTable[i] = buffer.ReadString();
			buffer.stringTable = stringTable;

			if (buffer.Seek(indexTablePos, 5))
			{
				cnt = buffer.ReadInt();
				for (int i = 0; i < cnt; i++)
				{
					int index = buffer.ReadUshort();
					int len = buffer.ReadInt();
					stringTable[index] = buffer.ReadString(len);
				}
			}

			buffer.Seek(indexTablePos, 1);

			PackageItem pi;

			if (assetNamePrefix == null)
				assetNamePrefix = string.Empty;
			else if (assetNamePrefix.Length > 0)
				assetNamePrefix = assetNamePrefix + "_";

			cnt = buffer.ReadShort();
			for (int i = 0; i < cnt; i++)
			{
				int nextPos = buffer.ReadInt();
				nextPos += buffer.position;

				pi = new PackageItem();
				pi.owner = this;
				pi.type = (PackageItemType)buffer.ReadByte();
				pi.id = buffer.ReadS();
				pi.name = buffer.ReadS();
				buffer.ReadS(); //path
				pi.file = buffer.ReadS();
				pi.exported = buffer.ReadBool();
				pi.width = buffer.ReadInt();
				pi.height = buffer.ReadInt();

				switch (pi.type)
				{
					case PackageItemType.Image:
						{
							pi.objectType = ObjectType.Image;
							int scaleOption = buffer.ReadByte();
							if (scaleOption == 1)
							{
								Rect rect = new Rect();
								rect.x = buffer.ReadInt();
								rect.y = buffer.ReadInt();
								rect.width = buffer.ReadInt();
								rect.height = buffer.ReadInt();
								pi.scale9Grid = rect;

								pi.tileGridIndice = buffer.ReadInt();
							}
							else if (scaleOption == 2)
								pi.scaleByTile = true;

							buffer.ReadBool(); //smoothing
							break;
						}

					case PackageItemType.MovieClip:
						{
							buffer.ReadBool(); //smoothing
							pi.objectType = ObjectType.MovieClip;
							pi.rawData = buffer.ReadBuffer();
							break;
						}

					case PackageItemType.Font:
						{
							pi.rawData = buffer.ReadBuffer();
							break;
						}

					case PackageItemType.Component:
						{
							int extension = buffer.ReadByte();
							if (extension > 0)
								pi.objectType = (ObjectType)extension;
							else
								pi.objectType = ObjectType.Component;
							pi.rawData = buffer.ReadBuffer();

							UIObjectFactory.ResolvePackageItemExtension(pi);
							break;
						}

					case PackageItemType.Atlas:
					case PackageItemType.Sound:
					case PackageItemType.Misc:
						{
							pi.file = assetNamePrefix + pi.file;
							break;
						}
				}
				_items.Add(pi);
				_itemsById[pi.id] = pi;
				if (pi.name != null)
					_itemsByName[pi.name] = pi;

				buffer.position = nextPos;
			}

			buffer.Seek(indexTablePos, 2);

			cnt = buffer.ReadShort();
			for (int i = 0; i < cnt; i++)
			{
				int nextPos = buffer.ReadShort();
				nextPos += buffer.position;

				string itemId = buffer.ReadS();
				pi = _itemsById[buffer.ReadS()];

				AtlasSprite sprite = new AtlasSprite();
				sprite.atlas = pi;
				sprite.rect.x = buffer.ReadInt();
				sprite.rect.y = buffer.ReadInt();
				sprite.rect.width = buffer.ReadInt();
				sprite.rect.height = buffer.ReadInt();
				sprite.rotated = buffer.ReadBool();
				_sprites[itemId] = sprite;

				buffer.position = nextPos;
			}

			if (buffer.Seek(indexTablePos, 3))
			{
				cnt = buffer.ReadShort();
				for (int i = 0; i < cnt; i++)
				{
					int nextPos = buffer.ReadInt();
					nextPos += buffer.position;

					if (_itemsById.TryGetValue(buffer.ReadS(), out pi))
					{
						if (pi.type == PackageItemType.Image)
						{
							pi.pixelHitTestData = new PixelHitTestData();
							pi.pixelHitTestData.Load(buffer);
						}
					}

					buffer.position = nextPos;
				}
			}

			if (!Application.isPlaying)
				_items.Sort(ComparePackageItem);

			buffer.Seek(indexTablePos, 0);
			cnt = buffer.ReadShort();
			_dependencies = new Dictionary<string, string>[cnt];
			for (int i = 0; i < cnt; i++)
			{
				Dictionary<string, string> kv = new Dictionary<string, string>();
				kv.Add("id", buffer.ReadS());
				kv.Add("name", buffer.ReadS());
				_dependencies[i] = kv;
			}

			return true;
		}

		static int ComparePackageItem(PackageItem p1, PackageItem p2)
		{
			if (p1.name != null && p2.name != null)
				return p1.name.CompareTo(p2.name);
			else
				return 0;
		}

		/// <summary>
		/// 
		/// </summary>
		public void LoadAllAssets()
		{
			int cnt = _items.Count;
			for (int i = 0; i < cnt; i++)
				GetItemAsset(_items[i]);
		}

		/// <summary>
		/// 
		/// </summary>
		public void UnloadAssets()
		{
			int cnt = _items.Count;
			for (int i = 0; i < cnt; i++)
			{
				PackageItem pi = _items[i];
				if (pi.type == PackageItemType.Atlas)
				{
					if (pi.texture != null)
						pi.texture.Unload();
				}
				else if (pi.type == PackageItemType.Sound)
				{
					if (pi.audioClip != null)
						pi.audioClip.Unload();
				}
			}

			if (unloadBundleByFGUI &&
				_resBundle != null)
			{
				_resBundle.Unload(true);
				_resBundle = null;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public void ReloadAssets()
		{
			if (_fromBundle)
				throw new Exception("FairyGUI: new bundle must be passed to this function");

			ReloadAssets(null);
		}

		/// <summary>
		/// 
		/// </summary>
		public void ReloadAssets(AssetBundle resBundle)
		{
			_resBundle = resBundle;
			_fromBundle = _resBundle != null;

			int cnt = _items.Count;
			for (int i = 0; i < cnt; i++)
			{
				PackageItem pi = _items[i];
				if (pi.type == PackageItemType.Atlas)
				{
					if (pi.texture != null && pi.texture.nativeTexture == null)
						LoadAtlas(pi);
				}
				else if (pi.type == PackageItemType.Sound)
				{
					if (pi.audioClip != null && pi.audioClip.nativeClip == null)
						LoadSound(pi);
				}
			}
		}

		void Dispose()
		{
			int cnt = _items.Count;
			for (int i = 0; i < cnt; i++)
			{
				PackageItem pi = _items[i];
				if (pi.type == PackageItemType.Atlas)
				{
					if (pi.texture != null)
					{
						pi.texture.Dispose();
						pi.texture = null;
					}
				}
				else if (pi.type == PackageItemType.Sound)
				{
					if (pi.audioClip != null)
					{
						pi.audioClip.Unload();
						pi.audioClip = null;
					}
				}
			}
			_items.Clear();

			if (unloadBundleByFGUI &&
				_resBundle != null)
			{
				_resBundle.Unload(true);
				_resBundle = null;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="resName"></param>
		/// <returns></returns>
		public GObject CreateObject(string resName)
		{
			PackageItem pi;
			if (!_itemsByName.TryGetValue(resName, out pi))
			{
				Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
				return null;
			}

			return CreateObject(pi, null);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="resName"></param>
		/// <param name="userClass"></param>
		/// <returns></returns>
		public GObject CreateObject(string resName, System.Type userClass)
		{
			PackageItem pi;
			if (!_itemsByName.TryGetValue(resName, out pi))
			{
				Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
				return null;
			}

			return CreateObject(pi, userClass);
		}

		public void CreateObjectAsync(string resName, CreateObjectCallback callback)
		{
			PackageItem pi;
			if (!_itemsByName.TryGetValue(resName, out pi))
			{
				Debug.LogError("FairyGUI: resource not found - " + resName + " in " + this.name);
				return;
			}

			AsyncCreationHelper.CreateObject(pi, callback);
		}

		GObject CreateObject(PackageItem item, System.Type userClass)
		{
			Stats.LatestObjectCreation = 0;
			Stats.LatestGraphicsCreation = 0;

			GetItemAsset(item);

			GObject g = null;
			if (item.type == PackageItemType.Component)
			{
				if (userClass != null)
					g = (GComponent)Activator.CreateInstance(userClass);
				else
					g = UIObjectFactory.NewObject(item);
			}
			else
				g = UIObjectFactory.NewObject(item);

			if (g == null)
				return null;

			_constructing++;
			g.packageItem = item;
			g.ConstructFromResource();
			_constructing--;
			return g;
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="resName"></param>
		/// <returns></returns>
		public object GetItemAsset(string resName)
		{
			PackageItem pi;
			if (!_itemsByName.TryGetValue(resName, out pi))
			{
				Debug.LogError("FairyGUI: Resource not found - " + resName + " in " + this.name);
				return null;
			}

			return GetItemAsset(pi);
		}

		public List<PackageItem> GetItems()
		{
			return _items;
		}

		public PackageItem GetItem(string itemId)
		{
			PackageItem pi;
			if (_itemsById.TryGetValue(itemId, out pi))
				return pi;
			else
				return null;
		}

		public PackageItem GetItemByName(string itemName)
		{
			PackageItem pi;
			if (_itemsByName.TryGetValue(itemName, out pi))
				return pi;
			else
				return null;
		}

		public object GetItemAsset(PackageItem item)
		{
			switch (item.type)
			{
				case PackageItemType.Image:
					if (item.texture == null)
						LoadImage(item);
					return item.texture;

				case PackageItemType.Atlas:
					if (item.texture == null)
						LoadAtlas(item);
					return item.texture;

				case PackageItemType.Sound:
					if (item.audioClip == null)
						LoadSound(item);
					return item.audioClip;

				case PackageItemType.Font:
					if (item.bitmapFont == null)
						LoadFont(item);

					return item.bitmapFont;

				case PackageItemType.MovieClip:
					if (item.frames == null)
						LoadMovieClip(item);

					return item.frames;

				case PackageItemType.Component:
					return item.rawData;

				case PackageItemType.Misc:
					return LoadBinary(item);

				default:
					return null;
			}
		}

		void LoadAtlas(PackageItem item)
		{
			string ext = Path.GetExtension(item.file);
			string fileName = item.file.Substring(0, item.file.Length - ext.Length);

			Texture tex = null;
			Texture alphaTex = null;
			DestroyMethod dm;

			if (_fromBundle)
			{
				if (_resBundle != null)
				{
#if (UNITY_5 || UNITY_5_3_OR_NEWER)
					tex = _resBundle.LoadAsset<Texture>(fileName);
#else
					tex = (Texture2D)_resBundle.Load(fileName, typeof(Texture2D));
#endif
				}
				else
					Debug.LogWarning("FairyGUI: bundle already unloaded.");

				dm = DestroyMethod.None;
			}
			else
				tex = (Texture)_loadFunc(fileName, ext, typeof(Texture), out dm);

			if (tex == null)
				Debug.LogWarning("FairyGUI: texture '" + item.file + "' not found in " + this.name);
			else if (!(tex is Texture2D))
			{
				Debug.LogWarning("FairyGUI: settings for '" + item.file + "' is wrong! Correct values are: (Texture Type=Default, Texture Shape=2D)");
				tex = null;
			}
			else
			{
				if (((Texture2D)tex).mipmapCount > 1)
					Debug.LogWarning("FairyGUI: settings for '" + item.file + "' is wrong! Correct values are: (Generate Mip Maps=unchecked)");
			}

			if (tex != null)
			{
				fileName = fileName + "!a";
				if (_fromBundle)
				{
					if (_resBundle != null)
					{
#if (UNITY_5 || UNITY_5_3_OR_NEWER)
						alphaTex = _resBundle.LoadAsset<Texture2D>(fileName);
#else
						alphaTex = (Texture2D)_resBundle.Load(fileName, typeof(Texture2D));
#endif
					}
				}
				else
					alphaTex = (Texture2D)_loadFunc(fileName, ext, typeof(Texture2D), out dm);
			}


			if (tex == null)
			{
				tex = NTexture.CreateEmptyTexture();
				dm = DestroyMethod.Destroy;
			}

			if (item.texture == null)
			{
				item.texture = new NTexture(tex, alphaTex, (float)tex.width / item.width, (float)tex.height / item.height);
				item.texture.destroyMethod = dm;
			}
			else
			{
				item.texture.Reload(tex, alphaTex);
				item.texture.destroyMethod = dm;
			}
		}

		void LoadImage(PackageItem item)
		{
			AtlasSprite sprite;
			if (_sprites.TryGetValue(item.id, out sprite))
				item.texture = new NTexture((NTexture)GetItemAsset(sprite.atlas), sprite.rect, sprite.rotated);
			else
				item.texture = NTexture.Empty;
		}

		void LoadSound(PackageItem item)
		{
			string ext = Path.GetExtension(item.file);
			string fileName = item.file.Substring(0, item.file.Length - ext.Length);

			AudioClip audioClip = null;
			DestroyMethod dm;

			if (_resBundle != null)
			{
#if (UNITY_5 || UNITY_5_3_OR_NEWER)
				audioClip = _resBundle.LoadAsset<AudioClip>(fileName);
#else
				audioClip = (AudioClip)_resBundle.Load(fileName, typeof(AudioClip));
#endif
				dm = DestroyMethod.None;
			}
			else
			{
				audioClip = (AudioClip)_loadFunc(fileName, ext, typeof(AudioClip), out dm);
			}

			if (item.audioClip == null)
				item.audioClip = new NAudioClip(audioClip);
			else
				item.audioClip.Reload(audioClip);
			item.audioClip.destroyMethod = dm;
		}

		byte[] LoadBinary(PackageItem item)
		{
			string ext = Path.GetExtension(item.file);
			string fileName = item.file.Substring(0, item.file.Length - ext.Length);

			TextAsset ta;
			if (_resBundle != null)
			{
#if (UNITY_5 || UNITY_5_3_OR_NEWER)
				ta = _resBundle.LoadAsset<TextAsset>(fileName);
#else
				ta = (TextAsset)_resBundle.Load(fileName, typeof(TextAsset));
#endif
				if (ta != null)
					return ta.bytes;
				else
					return null;
			}
			else
			{
				DestroyMethod dm;
				object ret = _loadFunc(fileName, ext, typeof(TextAsset), out dm);
				if (ret == null)
					return null;
				if (ret is byte[])
					return (byte[])ret;
				else
					return ((TextAsset)ret).bytes;
			}
		}

		void LoadMovieClip(PackageItem item)
		{
			ByteBuffer buffer = item.rawData;

			buffer.Seek(0, 0);

			item.interval = buffer.ReadInt() / 1000f;
			item.swing = buffer.ReadBool();
			item.repeatDelay = buffer.ReadInt() / 1000f;

			buffer.Seek(0, 1);

			int frameCount = buffer.ReadShort();
			item.frames = new MovieClip.Frame[frameCount];

			string spriteId;
			MovieClip.Frame frame;
			AtlasSprite sprite;
			Rect frameRect = new Rect();

			for (int i = 0; i < frameCount; i++)
			{
				int nextPos = buffer.ReadShort();
				nextPos += buffer.position;

				frame = new MovieClip.Frame();
				frameRect.x = buffer.ReadInt();
				frameRect.y = buffer.ReadInt();
				frameRect.width = buffer.ReadInt();
				frameRect.height = buffer.ReadInt();
				frame.addDelay = buffer.ReadInt() / 1000f;
				spriteId = buffer.ReadS();

				if (spriteId != null && _sprites.TryGetValue(spriteId, out sprite))
				{
					frame.texture = new NTexture((NTexture)GetItemAsset(sprite.atlas), sprite.rect, sprite.rotated,
						new Vector2(item.width, item.height), frameRect.position);
				}
				item.frames[i] = frame;

				buffer.position = nextPos;
			}
		}

		void LoadFont(PackageItem item)
		{
			BitmapFont font = new BitmapFont(item);
			item.bitmapFont = font;
			ByteBuffer buffer = item.rawData;

			buffer.Seek(0, 0);

			bool ttf = buffer.ReadBool();
			font.canTint = buffer.ReadBool();
			font.resizable = buffer.ReadBool();
			font.hasChannel = buffer.ReadBool();
			int fontSize = buffer.ReadInt();
			int xadvance = buffer.ReadInt();
			int lineHeight = buffer.ReadInt();

			float texScaleX = 1;
			float texScaleY = 1;
			NTexture mainTexture = null;
			AtlasSprite mainSprite = null;
			if (ttf && _sprites.TryGetValue(item.id, out mainSprite))
			{
				mainTexture = (NTexture)GetItemAsset(mainSprite.atlas);
				texScaleX = mainTexture.root.uvRect.width / mainTexture.width;
				texScaleY = mainTexture.root.uvRect.height / mainTexture.height;
			}

			buffer.Seek(0, 1);

			BitmapFont.BMGlyph bg;
			int cnt = buffer.ReadInt();
			for (int i = 0; i < cnt; i++)
			{
				int nextPos = buffer.ReadShort();
				nextPos += buffer.position;

				bg = new BitmapFont.BMGlyph();
				char ch = buffer.ReadChar();
				font.AddChar(ch, bg);

				string img = buffer.ReadS();
				int bx = buffer.ReadInt();
				int by = buffer.ReadInt();
				bg.offsetX = buffer.ReadInt();
				bg.offsetY = buffer.ReadInt();
				bg.width = buffer.ReadInt();
				bg.height = buffer.ReadInt();
				bg.advance = buffer.ReadInt();
				bg.channel = buffer.ReadByte();
				//The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha).
				if (bg.channel == 1)
					bg.channel = 2;
				else if (bg.channel == 2)
					bg.channel = 1;
				else if (bg.channel == 3)
					bg.channel = 0;
				else if (bg.channel == 8)
					bg.channel = 3;

				if (ttf)
				{
					if (mainSprite.rotated)
					{
						bg.uv[0] = new Vector2((float)(by + bg.height + mainSprite.rect.x) * texScaleX,
							1 - (float)(mainSprite.rect.yMax - bx) * texScaleY);
						bg.uv[1] = new Vector2(bg.uv[0].x - (float)bg.height * texScaleX, bg.uv[0].y);
						bg.uv[2] = new Vector2(bg.uv[1].x, bg.uv[0].y + (float)bg.width * texScaleY);
						bg.uv[3] = new Vector2(bg.uv[0].x, bg.uv[2].y);
					}
					else
					{
						bg.uv[0] = new Vector2((float)(bx + mainSprite.rect.x) * texScaleX,
							1 - (float)(by + bg.height + mainSprite.rect.y) * texScaleY);
						bg.uv[1] = new Vector2(bg.uv[0].x, bg.uv[0].y + (float)bg.height * texScaleY);
						bg.uv[2] = new Vector2(bg.uv[0].x + (float)bg.width * texScaleX, bg.uv[1].y);
						bg.uv[3] = new Vector2(bg.uv[2].x, bg.uv[0].y);
					}

					bg.lineHeight = lineHeight;
				}
				else
				{
					PackageItem charImg;
					if (_itemsById.TryGetValue(img, out charImg))
					{
						GetItemAsset(charImg);
						charImg.texture.GetUV(bg.uv);
						bg.width = charImg.texture.width;
						bg.height = charImg.texture.height;

						if (mainTexture == null)
							mainTexture = charImg.texture.root;
					}

					if (fontSize == 0)
						fontSize = bg.height;

					if (bg.advance == 0)
					{
						if (xadvance == 0)
							bg.advance = bg.offsetX + bg.width;
						else
							bg.advance = xadvance;
					}

					bg.lineHeight = bg.offsetY < 0 ? bg.height : (bg.offsetY + bg.height);
					if (bg.lineHeight < font.size)
						bg.lineHeight = font.size;
				}

				buffer.position = nextPos;
			}

			font.size = fontSize;
			font.mainTexture = mainTexture;
			if (!font.hasChannel)
				font.shader = ShaderConfig.imageShader;
		}
	}
}
